package com.ho1ho.springboot.framework.core.controllers;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryUsage;
import java.text.DecimalFormat;
import java.util.Map;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.joda.time.DateTime;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.WebRequest;

import com.ho1ho.springboot.framework.core.Const;
import com.ho1ho.springboot.framework.core.utils.Utils;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
// @ControllerAdvice
public class ErrorHandler implements ErrorController {

	private static final String ERROR_PATH = "/error";

	private static final String LS = System.getProperty("line.separator");

	@Resource
	protected Environment environment;

	@Override
	public String getErrorPath() {
		return ERROR_PATH;
	}

	@Resource
	private ErrorAttributes errorAttributes;

	// @ExceptionHandler(NoHandlerFoundException.class)
	// public String handle404Exception(NoHandlerFoundExceptionex) {
	// //do whatever you want
	// return "viewName";
	// }
	//
	// @ExceptionHandler(NullPointerException.class)
	// public String controllerExceptionHandler(HttpServletRequest req, Exception e)
	// {
	// logger.error("---ControllerException Handler---Host {} invokes url {} ERROR:
	// {}", req.getRemoteHost(), req.getRequestURL(), e.getMessage());
	// return e.getMessage();
	// }

	@GetMapping("/access_denied")
	public String accessDenied(HttpServletRequest req) {
		String uri = (String) req.getAttribute("javax.servlet.forward.request_uri");
		log.warn("accessDenied() username: [{}] uri: [{}]", Utils.getLoggedInUserName(), uri);
		return "error/access_denied";
	}

	@RequestMapping(ERROR_PATH)
	public String error(HttpServletRequest req, HttpServletResponse res, WebRequest webRequest, Model model) {
		String uri = (String) req.getAttribute("javax.servlet.forward.request_uri");
		log.error("Encountered an error. URI={}", uri);

		if (environment.acceptsProfiles(Const.CURRENT_RUNNING_PROFILE_DEV)) {
			Map<String, Object> attrs = errorAttributes.getErrorAttributes(webRequest, true);
			attrs.put("env", getSystemInfo(req));

			model.addAllAttributes(attrs);
			return "error/error";
		} else {
			return "error/404";
		}
	}

	private String getSystemInfo(HttpServletRequest req) {
		DecimalFormat df = new DecimalFormat("#0.00");

		MemoryUsage memUsage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
		String smemUsed = df.format((double) memUsage.getUsed() / (1024 * 1024));
		String smemMax = df.format((double) memUsage.getMax() / (1024 * 1024));

		double uptime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000.0;

		StringBuilder ibuf = new StringBuilder();
		ibuf.append(StringUtils.leftPad("Server version : ", 25) + req.getSession().getServletContext().getServerInfo() + LS);
		ibuf.append(StringUtils.leftPad("OS name : ", 25) + SystemUtils.OS_NAME + LS);
		ibuf.append(StringUtils.leftPad("OS version : ", 25) + SystemUtils.OS_VERSION + LS);
		ibuf.append(StringUtils.leftPad("OS architecture : ", 25) + SystemUtils.OS_ARCH + LS);
		ibuf.append(StringUtils.leftPad("JRE home : ", 25) + SystemUtils.JAVA_HOME + LS);
		ibuf.append(StringUtils.leftPad("JVM version : ", 25) + SystemUtils.JAVA_VERSION + LS);
		ibuf.append(StringUtils.leftPad("JVM vendor : ", 25) + SystemUtils.JAVA_VENDOR + LS);
		ibuf.append(StringUtils.leftPad("JVM uptime(in seconds): ", 25) + uptime + LS);
		ibuf.append(StringUtils.leftPad("JVM heap(in MB): ", 25) + smemUsed + "/" + smemMax + LS);
		ibuf.append(StringUtils.leftPad("System time : ", 25) + DateTime.now() + LS);
		return ibuf.toString();
	}
}